package users

import (
	"fmt"
	"strings"
	"time"

	"github.com/golang/glog"
	"modules.tenxcloud.com/common/composite"

	"github.com/jinzhu/gorm"
)

var (
	AdminUserType  = "admin"
	TenantUserType = "tenant"
	SiteUserType   = "site"

	RegisterSource_admin = "admin"
	RegisterSource_self  = "self"
)

type User struct {
	UserID           string    `gorm:"primary_key;column:id" json:"userid,omitempty"`
	UserName         string    `gorm:"column:username" json:"username,omitempty"`
	Account          string    `gorm:"column:account" json:"account,omitempty"`
	Password         string    `gorm:"column:password" json:"-"`
	RegistryTime     time.Time `gorm:"column:registry_time" json:"registry_time,omitempty"`
	LastLoginTime    time.Time `gorm:"column:lastlogin_time" json:"lastLoginTime,omitempty"`
	Phone            string    `gorm:"column:phone" json:"phone,omitempty"`
	Email            string    `gorm:"column:email" json:"email,omitempty"`
	Address          string    `gorm:"column:address" json:"address,omitempty"`
	ReferralCode     string    `gorm:"column:referral_code" json:"referralCode,omitempty"`
	ApiToken         string    `gorm:"column:api_token" json:"apiToken,omitempty"`
	Type             string    `gorm:"cloumn:type" json:"type,omitempty"` // user type: admin,tenant,site
	CompanyID        string    `gorm:"column:companyid" json:"companyid,omitempty"`
	Description      string    `gorm:"column:description" json:"description,omitempty"`
	Source           string    `gorm:"column:source" json:"source,omitempty"`
	ResetInitPwd     int       `gorm:"column:reset_init_pwd" json:"-"` // reset init pwd, 0 reset 1 done
	Namespace        string    `gorm:"column:namespace" json:"namespace"`
	CustomId         string    `gorm:"column:custom_id;default:99" json:"custom_id"`
	Vendors          string    `gorm:"column:vendors" json:"vendors"` // allow to access cloud vendor
	AccessKey        string    `gorm:"column:access_key" json:"accessKey"`
	SecretKey        string    `gorm:"column:secret_key" json:"secretKey"`
	JWTToken         string    `gorm:"-" json:"jwtToken,omitempty"`
	LoginStep        int       `gorm:"-" json:"login_step"` // tenant first login, 0 done 1 phone check 2 reset pwd 3 company identity
	Balance          float64   `gorm:"column:balance" json:"balance"`
	FrozenBalance    float64   `gorm:"column:frozen_balance" json:"frozen_balance"`
	CompanyStatus    int       `gorm:"-" json:"company_status,omitempty"` // 认证状态  1 已认证 2 未认证
	ClientIP         string    `gorm:"-" json:"clientIP,omitempty"`
	Region           string    `gorm:"-" json:"region,omitempty"`
	CloudStorageSize float64   `gorm:"column:cloud_storage_size" json:"cloud_storage_size"`
	UsedTrialCoupon  int       `gorm:"column:used_trial_coupon" json:"used_trial_coupon"`
}

func New() *User {
	return &User{}
}

func (u *User) TableName() string {
	return "user"
}

func (u *User) Create(db *gorm.DB) error {
	return db.Create(&u).Error
}

func (u *User) DeleteByID(db *gorm.DB, ids interface{}) error {
	return db.Table(u.TableName()).Where("userid in (?)", ids).Delete(User{}).Error
}

func (u *User) List(db *gorm.DB, sort, conditions map[string]interface{}, from, size int) (users []User, count int64, err error) {
	userType := []string{AdminUserType, TenantUserType, SiteUserType}
	db = db.Table(u.TableName()).Where("type in (?)", userType)
	if conditions != nil {
		for k, v := range conditions {
			if k == "" {
				continue
			}
			db = db.Where(k, v)
		}
	}
	db.Count(&count)
	if size != 0 {
		db = db.Limit(size)
	}
	if from != 0 {
		db = db.Offset(from)
	}

	if sort != nil {
		for k, v := range sort {
			db = db.Order(fmt.Sprintf("%s %s", k, v))
		}
	} else {
		db = db.Order("registry_time desc")
	}
	err = db.Find(&users).Error
	return
}

// GetByUsernameAndPassword fetch a user record by user_name and password
func (u *User) GetByUsernameAndPassword(db *gorm.DB, userName, password string) error {
	return db.Table(u.TableName()).Where("username = ?", userName).Where("password = ?", password).First(u).Error
}

// GetByUsernameAndPassword fetch a user record by user_name and password
func (u *User) GetByUsername(db *gorm.DB, userName string) error {
	return db.Table(u.TableName()).Where("username = ?", userName).First(u).Error
}
func (u *User) GetByPhone(db *gorm.DB, phone string) error {
	return db.Table(u.TableName()).Where("phone = ?", phone).First(u).Error
}

func (u *User) GetByUsernameOrAccount(db *gorm.DB, userName, account string) (user User, err error) {
	err = db.Table(u.TableName()).Where("username = ?", userName).Or("account = ?", account).First(&user).Error
	return
}

func (u *User) GetByAccount(db *gorm.DB, account string) error {
	return db.Table(u.TableName()).Where("account = ?", account).First(u).Error
}

func (u *User) Update(db *gorm.DB, user *User) error {
	return db.Model(u).Where("id = ?", u.UserID).Updates(user).Error
}
func (u *User) UpdatePartFields(db *gorm.DB, fields map[string]interface{}) error {
	return db.Table(u.TableName()).Where("id = ?", u.UserID).Update(fields).Error
}
func (u *User) UpdateByConditions(db *gorm.DB, conditions map[string]interface{}) error {
	return db.Table(u.TableName()).Where("id = ?", u.UserID).Updates(conditions).Error
}

func (u *User) GetByUserId(db *gorm.DB) error {
	return db.Table(u.TableName()).Where("id = ?", u.UserID).First(u).Error

}
func (u *User) GetByUserIdForUpdate(db *gorm.DB) error {
	return db.Table(u.TableName()).Set("gorm:query_option", "FOR UPDATE").Where("id = ?", u.UserID).First(u).Error
}
func (m *User) GetType() string      { return m.Type }
func (m *User) GetUserName() string  { return m.UserName }
func (m *User) GetCompanyID() string { return m.CompanyID }
func (u *User) GetByAccessKey(db *gorm.DB, accessKey string) error {
	return db.Table(u.TableName()).Where("access_key = ?", accessKey).First(u).Error
}
func (u *User) IsAdmin() bool {
	return u.Type == AdminUserType // 判断用户类型是否为 "admin"
}

type UpdateUser struct {
	UserID      string `gorm:"column:userid" json:"userid,omitempty"`
	UserName    string `gorm:"column:username" json:"username,omitempty"`
	Account     string `gorm:"column:account" json:"account,omitempty"`
	Password    string `gorm:"column:password" json:"password,omitempty"`
	Phone       string `gorm:"column:phone" json:"phone,omitempty"`
	Email       string `gorm:"column:email" json:"email,omitempty"`
	Address     string `gorm:"column:address" json:"address,omitempty"`
	CompanyID   string `gorm:"column:companyid" json:"companyid,omitempty"`
	Description string `gorm:"column:description" json:"description,omitempty"`
	Vendors     string `gorm:"column:vendors" json:"vendors"`
}

type Validate struct {
	Phone string `json:"phone"`
	Email string `json:"email"`
	Name  string `json:"username"` // exclude user info when update
}

func (v *Validate) Check() error {
	db, err := composite.Database.GetOrm()
	if err != nil {
		glog.Error(err)
		return err
	}

	user := &User{}
	users, _, err := user.List(db, nil, nil, 0, 0)
	if err != nil {
		glog.Error(err)
		return err
	}

	for _, u := range users {
		if v.Name == u.UserName {
			continue
		}
		if v.Phone != "" && v.Phone == u.Phone {
			return fmt.Errorf("phone %s has been used", v.Phone)
		}
		if v.Email != "" && strings.ToLower(v.Email) == strings.ToLower(u.Email) {
			return fmt.Errorf("email %s has been used", v.Email)
		}
	}
	return nil
}

func (u *User) IncreseBalance(db *gorm.DB, userId string, amount float64) error {
	return db.Table(u.TableName()).Where("id = ?", userId).Update("balance", gorm.Expr("balance + ?", amount)).Error
}

func (u *User) DecreaseBalance(db *gorm.DB, userId string, amount float64) error {
	return db.Table(u.TableName()).Where("id = ?", userId).Update("balance", gorm.Expr("balance - ?", amount)).Error
}
